package skymeta

import (
	"context"
	"errors"
	"os"
	"os/signal"
	"syscall"

	"gitee.com/simonxie979/skymeta/protocol"
	"gitee.com/simonxie979/skymeta/uuid"

	"github.com/gogo/protobuf/proto"
	"github.com/joho/godotenv"
)

var (
	g_Context context.Context
	g_Cancel  context.CancelFunc

	msgCallTimeout string // Call超时响应消息名
)

func init() {
	msgCallTimeout = proto.MessageName(&protocol.CallTimeout{})
}

func Init(dotenv string) {
	if err := godotenv.Load(dotenv); err != nil {
		panic(err)
	}

	g_Context, g_Cancel = context.WithCancel(context.Background())

	init_Logger()
	logger.Warnf("skymeta", "skymeta launch start")
	defer logger.Warnf("skymeta", "skymeta launch finish")

	init_NodeID()
	init_Handler()
	init_ServiceCenter()
	init_Cluster()
}

func Shutdown() {
	g_Cancel()

	exit_Cluster()
	exit_ServiceCenter()
	exit_Handler()
	exit_Logger()

	os.Exit(0)
}

func Run() {
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT)

	for {
		select {
		case <-g_Context.Done():
			return
		case sig := <-sigChan:
			logger.Warnf("skymeta", "revice signal %v, shutdown process.", sig)
			Shutdown()
			return
		}
	}
}

func Send(source, destination uint64, msgName string, msg []byte) {
	ssMsg := &protocol.SSMessage{}
	ssMsg.Type = protocol.MessageType_Send
	ssMsg.Source = source
	ssMsg.Destination = destination
	ssMsg.Sequence = 0
	ssMsg.Name = msgName
	ssMsg.Body = msg

	srv := center.GetService(destination)
	if srv == nil {
		return
	}

	if srv.IsLocal() {
		srv.inPipe <- ssMsg
	} else {
		node.SendMsg(srv.GetSessionID(), ssMsg)
	}
}

func Call(source, destination uint64, msgName string, msg []byte) (respSession, respSource uint64, respName string, respData []byte, err error) {
	ssMsg := &protocol.SSMessage{}
	ssMsg.Type = protocol.MessageType_Call
	ssMsg.Source = source
	ssMsg.Destination = destination
	ssMsg.Sequence = uuid.GetGuid()
	ssMsg.Name = msgName
	ssMsg.Body = msg

	srv := center.GetService(destination)
	if srv == nil {
		err = errors.New("%v service not found")
		return
	}

	ch := make(chan *protocol.SSMessage, 1)
	defer close(ch)

	if err = addCall(ssMsg.Sequence, ch); err != nil {
		return
	}

	if srv.IsLocal() {
		srv.inPipe <- ssMsg
	} else {
		node.SendMsg(srv.GetSessionID(), ssMsg)
	}

	resp := <-ch
	respSession = resp.GetSequence()
	respSource = resp.GetSource()
	respName = resp.GetName()
	respData = resp.GetBody()
	if respName == msgCallTimeout {
		err = errors.New("call timeout")
	}
	return
}

func Response(source, destination, sequence uint64, msgName string, msg []byte) {
	ssMsg := &protocol.SSMessage{}
	ssMsg.Type = protocol.MessageType_Resp
	ssMsg.Source = source
	ssMsg.Destination = destination
	ssMsg.Sequence = sequence
	ssMsg.Name = msgName
	ssMsg.Body = msg

	srv := center.GetService(destination)
	if srv == nil {
		return
	}

	if srv.IsLocal() {
		srv.inPipe <- ssMsg
	} else {
		node.SendMsg(srv.GetSessionID(), ssMsg)
	}
}
